package com.gitee.zou8944.ipersist.utils

import com.gitee.zou8944.ipersist.core.bean.SqlStatement
import com.gitee.zou8944.ipersist.core.bean.SqlType
import java.beans.Introspector
import java.sql.Connection
import java.sql.PreparedStatement
import java.sql.ResultSet
import java.sql.Timestamp

object Executor {

    fun execute(connection: Connection, sqlStatement: SqlStatement, entity: Any?): Any {
        // sql预处理
        val (preparedSql, parameterKeys) = parseSql(sqlStatement.sql)
        // 参数处理
        val preparedStatement = connection
            .prepareStatement(preparedSql)
            .prepareParameter(parameterKeys, sqlStatement.parameterType, entity)
        // 执行及结果处理
        return when (sqlStatement.sqlType) {
            SqlType.SELECT -> {
                val resultSet = preparedStatement.executeQuery()
                val results = mutableListOf<Any>()
                while (resultSet.next()) {
                    results.add(resultSet.toObject(sqlStatement.resultType!!))
                }
                results
            }
            SqlType.INSERT,
            SqlType.UPDATE,
            SqlType.DELETE -> {
                preparedStatement.executeUpdate()
            }
        }
    }

    private fun parseSql(rawSql: String): Pair<String, List<String>> {
        val parameterKeys = mutableListOf<String>()
        val preparedSql = rawSql.replace(Regex("#\\{\\s*(\\w+)\\s*}")) { matchResult ->
            parameterKeys.add(matchResult.groupValues[1])
            "?"
        }
        return preparedSql to parameterKeys
    }

    private fun PreparedStatement.prepareParameter(
        parameterKeys: List<String>,
        parameterType: Class<*>?,
        parameterEntity: Any?
    ): PreparedStatement {
        // FIXME 一刀切，应考虑多种情况的
        if (parameterKeys.isEmpty() || parameterType == null || parameterEntity == null) {
            return this
        }
        val entityProperties = Introspector.getBeanInfo(parameterType).propertyDescriptors
        parameterKeys.forEachIndexed { index, key ->
            val entityProperty = entityProperties.first { compareFieldNameIgnoreStashAndCamel(key, it.name) }
            val entityPropertyType = entityProperty.propertyType
            val entityPropertyValue = entityProperty.readMethod.invoke(parameterEntity)
            when (entityPropertyType) {
                String::class.java -> this.setString(index + 1, entityPropertyValue as String?)
                Integer::class.java -> this.setInt(index + 1, entityPropertyValue as Int)
                Timestamp::class.java -> this.setTimestamp(index + 1, entityPropertyValue as Timestamp?)
            }
        }
        return this
    }

    private fun ResultSet.toObject(resultType: Class<*>): Any {
        val resultEntity = resultType.getDeclaredConstructor().newInstance()
        val resultProperties = Introspector.getBeanInfo(resultType).propertyDescriptors
        val resultSetMetaData = this.metaData

        for (i in 1..resultSetMetaData.columnCount) {
            val columnName = resultSetMetaData.getColumnName(i)
            val resultProperty = resultProperties.first { compareFieldNameIgnoreStashAndCamel(columnName, it.name) }
            val resultPropertyType = resultProperty.propertyType
            val resultPropertySetter = resultProperty.writeMethod
            // todo 这里还需要对比数据库读出的类型和目标类型是否匹配，数据库名称和目标名称是否匹配
            val columnValue: Any = when (resultPropertyType) {
                String::class.java -> this.getString(i)
                Integer::class.java -> this.getInt(i)
                Timestamp::class.java -> this.getTimestamp(i)
                else -> throw Exception("Need more type support")
            }
            resultPropertySetter.invoke(resultEntity, columnValue)
        }

        return resultEntity
    }

    /**
     * 字段名比较器，忽略下划线和驼峰
     */
    private fun compareFieldNameIgnoreStashAndCamel(dbField: String, beanField: String): Boolean {
        return beanField == dbField.split("_").joinToString("") { it.capitalize() }.decapitalize()
    }

}

